חידה לחימום בסל מקש יש צמר. כדורי 00 שני שחקנים משחקים בתורות: כל שחקן, בתורו, צריך להוציא כמות כלשהי של כדורי צמר מהסל לפחות כדור אחד, אך לא יותר ממחצית מכמות כדורי הצמר שבסל. מי שלא יכול לעשות מהלך (מתי זה יקרה?) מפסיד במשחק. פתחו אסטרטגיה מנצחת עבור אחד השחקנים במשחק.
. הגדרת הערימה בשיעור קודם קדימויות'. מבני נתונים ויעילות אלגוריתמים (4..05) ראינו מבני נתונים שונים למימוש טיפוס הנתונים המופשט (טנ"מ) 'תור הקרוי אחד ממבני הנתונים היעילים ביותר למימוש טנ"מ זה הוא מבנה נתונים הנקרא ערימה בינארית Heap),(Binary או בקיצור ערימה.(Heap) ערימה בינארית היא עץ בינארי כמעט שלם, הממומש באמצעות מערך, והמתאפיין בתכונה נוספת הנקראת תכונת הערימה Property).(Heap כזכור, עץ בינארי כמעט שלם הוא עץ שמלא לחלוטין בכל רמותיו, פרט אולי לאחרונה, המלאה משמאל ועד לנקודה מסוימת. יתאפיין בשתי התכונות הבאות: המערך,A A.length שבו נאחסן את איברי העץ הבינארי הכמעט שלם, מספר התאים במערך, A.heap-size מספר האיברים בערימה המאוחסנת ב- A. במילים אחרות, אף כי A[0..A.length-] זהו המערך כולו, איבר שנמצא אחרי A[A.heap-size-] אינו שייך לערימה. שורשו של העץ הוא [0]A, ובהינתן האינדקס i של צומת, אפשר לחשב בקלות את האינדקסים של אביו, של בנו השמאלי, ושל בנו הימני: LEFT(i) return i+ RIGHT(i) return i+ PARENT(i) return ( i ) / כעת, כדי שעץ בינארי כמעט שלם המיוצג במערך ייחשב כערימה, עליו לקיים את תכונת הערימה: לכל צומת i, פרט לשורש, מתקיים: A[i]. A[PARENT(i)] > כלומר, ערכו של כל צומת הוא לכל היותר ערכו של ההורה שלו. לפיכך, הערך הגדול ביותר בערימה מאוחסן בשורש, והערך שצומת מכיל הוא גדול או שווה לכל ערכי צאצאיו.
שאלה האם המערך (3,7,0,3,6,4) A מייצג ערימה? אם כן שרטטו את ייצוג הערימה כעץ בינארי כמעט שלם. מושג הערימה, כפי שהגדרנו אותו לעיל, נקרא ערימת מקסימום Heap).(Maximum קיים סוג נוסף של ערימה, הנקרא ערימת מינימום Heap),(Minimum שזהה בכל לערימת מקסימום, פרט לכך שתכונת הערימה נראית במקרה הזה כך: לכל צומת i, פרט לשורש, מתקיים: A[i]. A[PARENT(i)] < כלומר, ערכו של כל צומת הוא לכל הפחות ערכו של ההורה שלו. לפיכך, הערך הקטן ביותר בערימת מינימום מאוחסן בשורש, והערך שצומת מכיל הוא קטן או שווה מכל ערכי צאצאיו. שאלה א. ב. הראו שהמערך (,0,7,9,5,6,4,8,,3,) A מייצג ערימת מקסימום, ושרטטו את ייצוג הערימה כעץ בינארי כמעט שלם. הראו כיצד ניתן לסדר מחדש את האיברים במערך A, כך שהוא ייצג ערימת מינימום. שאלה א. ב. האם מערך הממוין בסדר יורד בהכרח מייצג ערימת מקסימום? האם מערך הממוין בסדר עולה בהכרח מייצג ערימת מינימום? בערימה בינארית מתקיימות שלוש הטענות הבאות: טענה : אם A הוא מערך המייצג ערימה בינארית, ואם נסמן ב- n את,A.heap-size אז כל האיברים שבמערך שנמצאים בין האינדקס n/ לבין האינדקס -n, הם עלים.. log n טענה : גובהה של ערימה בינארית בעלת n צמתים הוא n. h+ טענה 3: בערימה בינארית בעלת n צמתים, מספר הצמתים שגובהם h הוא לכל היותר 3
. שמירה על תכונת הערימה נכיר אלגוריתם חשוב לטיפול בערימות מקסימום בשם.MAX-HEAPIFY האלגוריתם מקבל כקלט מערך A ואינדקס i למערך. ההנחה על הקלט היא, שכאשר מתבצעת קריאה לאלגוריתם,MAX-HEAPIFY אז העצים הבינאריים המושרשים ב-( LEFT(i וב-( RIGHT(i מקסימום חוקיות, אולם ייתכן ש-[ A[i קטן מבניו, ובכך מפר את תכונת הערימה. MAX-HEAPIFY תפקידה של באינדקס i יהפוך לערימת מקסימום חוקית. הם ערימות הוא "להחליק" את A[i] במורד הערימה, עד שהתת-עץ המושרש MAX-HEAPIFY(A,i) L LEFT(i) R RIGHT(i) 3 if L < A.heap-size and A[L] > A[i] 4 then largest L 5 else largest i 6 if R < A.heap-size and A[R] > A[largest] 7 then largest R 8 if largest i 9 then SWAP(A[i],A[largest]) 0 MAX-HEAPIFY(A,largest ) נדגים את פעולת האלגוריתם MAX-HEAPIFY על מערך A המקיים 0 A.heap-size. אפשר לראות כי האיבר []A במערך הנתון מפר את תכונת הערימה, שכן הוא אינו גדול משני בניו: 3 החלפת []A ו-[ A[3 תוביל לכך שצומת אכן יקיים את תכונת הערימה, אולם צומת אינו מקיים אותה, ולכן - נבצע קריאה רקורסיבית.MAX-HEAPIFY(A,3) 4
החלפת A[8]-וA[3] זה בזה מסדרת את צומת 3, והקריאה הרקורסיבית אינה גורמת לשינוי נוסף. MAX-HEAPIFY(A,8) -9?n מהו זמן הריצה של האלגוריתם MAX-HEAPIFY על תת-עץ בגודל שורות אורכות זמן קבוע, וגודלו של העץ, עליו מפעילים את הזימון הרקורסיבי בשורה 0, הוא לכל היותר 3/n (זה קורה כאשר בדיוק מחצית מהשורה התחתונה בעץ היא מלאה, ומפעילים את הזימון הרקורסיבי על תת-העץ השמאלי). לכן, נוסחת הנסיגה המבטאת את זמן הריצה של האלגוריתם MAX-HEAPIFY היא: T(n) T(n/3) + Θ() לפי משפט האב, נקבל שהפתרון הוא Θ(logn),T(n) כלומר לוגריתמי במספר הצמתים בעץ (או: לינארי כגובהו של העץ.(Θ(h) שאלה MAX-HEAPIFY(A,) בעזרת שרטוט תארו את פעולת האלגוריתם כאשר מפעילים אותו על המערך (5,4,,9,8,0,,5,,7,8,8).A 5
3. בניית ערימה ניתן להשתמש באלגוריתם MAX-HEAPIFY מכיוון שהאיברים שנמצאים בין האינדקס כדי ליצור ערימת מקסימום ממערך.A[0..n-] n/ (אמצע המערך) לבין האינדקס (סוף -n המערך) הם כולם עלים של העץ, הרי שבתחילת התהליך כל אחד מהם הוא ערימת מקסימום בת איבר אחד. האלגוריתם BUILD-MAX-HEAP עובר על שאר הצמתים בעץ הבינארי, ומפעיל את MAX-HEAPIFY על כל אחד מהם. סדר המעבר על הצמתים, מהצמתים בעלי אינדקס גבוה לצמתים בעלי אינדקס נמוך (ובמערך זה בא לידי ביטוי במעבר מימין לשמאל) מבטיח שהתת- עצים המושרשים בבניו של צומת i הם ערימות מקסימום חוקיות, לפני ש- MAX-HEAPIFYמופעל על צומת זה. BUILD-MAX-HEAP(A) n A.heap-size A.length for i n / downto 0 3 do MAX-HEAPIFY(A,i) נדגים את פעולת האלגוריתם, על המערך הבא (בן 0 איברים) המייצג עץ בינארי כמעט שלם. ניתן לראות שהאינדקס i מקבל את הערך ההתחלתי 4 (באיטרציה הראשונה של הלולאה): כך נראה העץ לאחר קריאה אחת ל- MAX-HEAPIFY. האינדקס i יקבל כעת את הערך 3: כך יראה העץ באיטרציות הבאות של הלולאה. נשים לב שבכל פעם שמזמנים את MAX-HEAPIFY על צומת מסוים, אז שני תתי-העצים של הצומת הם ערימות מקסימום חוקיות: 6
מהי סיבוכיות זמן הריצה של האלגוריתם?BUILD-MAX-HEAP ניתן למצוא חסם עליון אסימפטוטי, באופן הבא: כל קריאה ל- MAX-HEAPIFY רצה בזמן לוגריתמי ויש /n קריאות כאלה, לכן החסם העליון הוא.O(nlogn) חסם זה, הגם שהוא נכון, איננו חסם הדוק. לצורך מציאת החסם ההדוק, יהיה עלינו להיעזר, בנוסף לטענות שראינו לעיל, גם בנוסחה הבאה: x x ( x) טענה :4 אם מתקיים < x, אז כעת, נוכל לקבל חסם הדוק יותר, אם נשים לב כי זמן הריצה של MAX-HEAPIFY על צומת משתנה עם גובה הצומת בעץ, וכי רוב הצמתים אינם גבוהים. נסתמך על שתי הטענות הבאות: גובהה של ערימה בת n איברים הוא היותר log n. n h +, ומספר הצמתים בכל גובה h בערימה כזו הוא לכל הזמן הדרוש להרצת MAX-HEAPIFY על צומת שגובהו h הוא,Θ(h) ולכן מתקיים גם.O(h) 7
ניתן לבטא את זמן הריצה הכולל של האלגוריתם BUILD-MAX-HEAP באופן הבא: + יש לכל היותר יש לכל היותר יש לכל היותר... יש לכל היותר n צמתים בגובה, ועבור כל אחד מהם מבצעים ()O פעולות. n צמתים בגובה, ועבור כל אחד מהם מבצעים ()O פעולות. n צמתים בגובה 3, ועבור כל אחד מהם מבצעים (3)O פעולות., ועבור כל אחד מבצעים O(logn) פעולות. log n צמתים בגובה n + 3+ log n + logn h n O( h) h+ נסכם הכול יחד, ונקבל: נכניס את הסכום לתוך הסימון האסימפטוטי O, ונקבל: O( logn h n ( h h + )) ניתן להשמיט את ערך התקרה מבלי לפגום בנכונות הטענה: O( O( logn h logn h O( n n ( h ( logn h h h+ n h+ h h + )) כפל זו פעולה המקיימת את חוק החילוף: )) ) ניתן להוציא את n אל מחוץ לסכום: 8
כדי למצוא חסם עליון לסכום שבסוגריים, ניעזר בטענה 4: x 0.5 0.5 ( 0.5) x ( x) 0.5 ( 0.5) 0.5 0.5 נציב 0.5,x ונקבל: O( n logn h + ואת השיוויון כעת: אפשר לחלק ב-, ואז נקבל: h h ) O( n ) O( n ) O( n) h + h+ h בסך הכל, הראינו שזמן הריצה של האלגוריתם BUILD-MAX-HEAP חסום מלמעלה על-ידי ביטוי לינארי. מאידך, ברור שהוא חסום גם מלמטה על-ידי ביטוי לינארי (כי עוברים בלולאה על /n איברים במערך). מכך שמתקיים גם O(n) וגם Ω(n), נסיק שמתקיים Θ(n). 4. מיון-ערימה ערימה היא מבנה נתונים שניתן להשתמש בו כדי לממש אלגוריתם מיון יעיל, הנקרא 'מיון ערימה' Sort).(Heap בהינתן מערך, נפעיל את BUILD-MAX-HEAP כדי להפוך אותו לערימת מקסימום. כעת, האיבר הגדול ביותר במערך מאוחסן ב-[ A[0. ניתן להציב אותו במקומו הסופי הנכון, על-ידי החלפתו עם.A[n-] נשים לב שאם "מסלקים" עכשיו את הצומת n מן הערימה, על-ידי הקטנת A.heap-size ב-, אפשר להפוך בקלות את A[0..n-] לערימה חוקית: בניו של השורש נותרו ערימות חוקיות, אולם ייתכן שהשורש החדש מפר את תכונת הערימה. כל מה שדרוש כדי להחזיר את תכונת הערימה, זו 9
קריאה ל-( MAX-HEAPIFY(A,0. עם סיום הקריאה, A[0..n-] יכיל ערימת מקסימום חוקית המורכבת מ- n- איברים. על ערימה זו נוכל לחזור על התהליך שוב ושוב, ובכל פעם הערימה תקטן באיבר אחד. HEAP-SORT(A) BUILD-MAX-HEAP(A) for i A.length- downto 3 do SWAP(A[0],A[i]) 4 A.heap-size A.heap-size 5 MAX-HEAPIFY(A,0) נדגים את פעולת האלגוריתם למיון-ערימה : 0
מהי סיבוכיות זמן הריצה של האלגוריתם? צעד מספר אורך זמן לינארי, וכל אחת מ- n- הקריאות ל- MAX-HEAPIFY מתבצעת בזמן לוגריתמי לגודל הערימה, אבל על ערימה שגובהה הולך וקטן. סך כל הפעולות שמתבצעות על-ידי זימונים אלו הוא: log( n ) + log( n ) + log( n 3) +... + log() + log() log(( n ) ( n )... ) log(( n )!) n! log( ) n log( n!) logn Θ( nlogn) logn Θ( nlogn) לכן, בסך-הכול, הסיבוכיות של מיון-ערימה היא logn). Θn ( 5. תור קדימויות מבנה הנתונים 'ערימה בינארית' מהווה דרך טובה מאוד לממש את טיפוס הנתונים המופשט 'תור קדימויות'. לשם כך, עלינו לספק מימוש לשלוש פעולות הממשק הבאות: הוספת איבר חדש, אחזור ערכו של האיבר המקסימלי, והוצאת האיבר המקסימלי. אחזור ערכו של האיבר המקסימלי ימומש בצורה טריוויאלית, ובזמן קבוע ()Θ, באופן הבא: MAXIMUM(A) return A[0] את הפעולה של הוצאת האיבר המקסימלי נממש כך: HEAP-EXTRACT-MAX(A) max A[0] A[0] A[A.heap-size-] 3 A.heap-size A.heap-size 4 MAX-HEAPIFY(A,0) 5 return max
ש, ברור שסיבוכיות זמן הריצה של הפעולה מהזימון של HEAP-EXTRACT-MAX היא, Θ(log n) שכן לבד,MAX-HEAPIFY המתבצע בזמן לוגריתמי, כל יתר הפעולות מתבצעות בזמן קבוע. שאלה הדגימו את פעולת האלגוריתם HEAP-EXTRACT-MAX על המערך (0,7,5,,6,4).A את הפעולה של הוספת איבר חדש ל- A, בעל עדיפות,ey נממש באמצעות הוספת עלה חדש לעץ. לאחר מכן, נסרוק מסלול מהעלה הזה לעבר השורש, כדי למצוא מקום מתאים לאיבר החדש: HEAP-MAX-INSERT(A,ey) A.heap-size A.heap-size + i A.heap-size- 3 while i > 0 and A[PARENT(i)] < ey 4 do A[i] A[PARENT(i)] 5 i PARENT(i) 6 A[i] ey זמן ריצת האלגוריתם על ערימה בת n איברים הוא (n Θ(log כן אורכו של המסלול מן העלה החדש ועד לשורש הוא (n. Θ(log HEAP-MAX-INSERT שאלה הדגימו את פעולת האלגוריתם על המערך (,8,9,6,7,3,5,,4,) A.